﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Pahan.McuFusion.IO
{
    internal abstract class ParallelPortImpl : IParallelPortImpl
    {
        protected ParallelPortImpl()
        {
            InitReaders();
            InitWriters();
        }

        #region Implementation of IParallelPortImpl

        /// <summary>
        /// Opens the port.
        /// </summary>
        public void Open()
        {
            AssertIsClosed();
            DoOpen();
            IsOpened = true;
        }

        /// <summary>
        /// Closes the port.
        /// </summary>
        public void Close()
        {
            DoClose();
            IsOpened = true;
        }

        /// <summary>
        /// Gets the value indicating whether the port is opened.
        /// </summary>
        public bool IsOpened
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the base address.
        /// </summary>
        public ushort BaseAddress
        {
            get
            {
                return baseAddress;
            }
            set
            {
                AssertIsClosed();
                baseAddress = value;
            }
        }

        /// <summary>
        /// Gets or sets the value of data i/o port.
        /// </summary>
        public byte DataPort
        {
            get
            {
                AssertIsOpened();
                //return dataPort;
                return In(baseAddress);
            }
            set
            {
                AssertIsOpened();
                Out(baseAddress, value);
                //dataPort = value;
            }
        }

        /// <summary>
        /// Gets or sets the value of control i/o port.
        /// </summary>
        public byte ControlPort
        {
            get
            {
                AssertIsOpened();
                return In((ushort)(baseAddress + 2));
            }
            set
            {
                AssertIsOpened();
                Out((ushort)(baseAddress + 2), value);
            }
        }

        /// <summary>
        /// Gets the value of status i/o port.
        /// </summary>
        public byte StatusPort
        {
            get
            {
                AssertIsOpened();
                return In((ushort)(baseAddress + 1));
            }
        }

        /// <summary>
        /// Reads the value from the specified pin.
        /// </summary>
        public bool ReadPin(ParallelPortPins pin)
        {
            AssertIsOpened();
            return pinReaders[pin]();
        }

        /// <summary>
        /// Writes the value to the specified pin.
        /// </summary>
        public void WritePin(ParallelPortPins pin, bool value)
        {
            AssertIsOpened();
            pinWriters[pin](value);
        }

        #endregion

        #region Protected Members

        /// <summary>
        /// Opens the port.
        /// </summary>
        protected abstract void DoOpen();

        /// <summary>
        /// Closes the port.
        /// </summary>
        protected abstract void DoClose();

        /// <summary>
        /// Reads the value from ther specified i/o port.
        /// </summary>
        protected abstract byte In(ushort address);

        /// <summary>
        /// Writes the value to the specified i/o port.
        /// </summary>
        protected abstract void Out(ushort address, byte value);

        #endregion

        #region Private Members

        private ushort baseAddress;

        private IDictionary<ParallelPortPins, Action<bool>> pinWriters;

        private IDictionary<ParallelPortPins, Func<bool>> pinReaders;

        private void InitReaders()
        {
            pinWriters = new Dictionary<ParallelPortPins, Action<bool>>
            {
                {ParallelPortPins.Pin01, value => {if (value) ControlPort |= 0x01; else ControlPort &= 0xFE;}},
                {ParallelPortPins.Pin02, value => {if (value) DataPort |= 0x01; else DataPort &= 0xFE;}},
                {ParallelPortPins.Pin03, value => {if (value) DataPort |= 0x02; else DataPort &= 0xFD;}},
                {ParallelPortPins.Pin04, value => {if (value) DataPort |= 0x04; else DataPort &= 0xFB;}},
                {ParallelPortPins.Pin05, value => {if (value) DataPort |= 0x08; else DataPort &= 0xF7;}},
                {ParallelPortPins.Pin06, value => {if (value) DataPort |= 0x10; else DataPort &= 0xEF;}},
                {ParallelPortPins.Pin07, value => {if (value) DataPort |= 0x20; else DataPort &= 0xDF;}},
                {ParallelPortPins.Pin08, value => {if (value) DataPort |= 0x40; else DataPort &= 0xBF;}},
                {ParallelPortPins.Pin09, value => {if (value) DataPort |= 0x80; else DataPort &= 0x7F;}},
                {ParallelPortPins.Pin10, value => {throw new NotSupportedException();}},
                {ParallelPortPins.Pin11, value => {throw new NotSupportedException();}},
                {ParallelPortPins.Pin12, value => {throw new NotSupportedException();}},
                {ParallelPortPins.Pin13, value => {throw new NotSupportedException();}},
                {ParallelPortPins.Pin14, value => {if (value) ControlPort |= 0x02; else ControlPort &= 0xFD;}},
                {ParallelPortPins.Pin15, value => {throw new NotSupportedException();}},
                {ParallelPortPins.Pin16, value => {if (value) ControlPort |= 0x04; else ControlPort &= 0xFB;}},
                {ParallelPortPins.Pin17, value => {if (value) ControlPort |= 0x08; else ControlPort &= 0xF7;}}
            };
        }

        private void InitWriters()
        {
            pinReaders = new Dictionary<ParallelPortPins, Func<bool>>
            {
                {ParallelPortPins.Pin01, () => (ControlPort & 0x01) != 0},
                {ParallelPortPins.Pin02, () => (DataPort & 0x01) != 0},
                {ParallelPortPins.Pin03, () => (DataPort & 0x02) != 0},
                {ParallelPortPins.Pin04, () => (DataPort & 0x04) != 0},
                {ParallelPortPins.Pin05, () => (DataPort & 0x08) != 0},
                {ParallelPortPins.Pin06, () => (DataPort & 0x10) != 0},
                {ParallelPortPins.Pin07, () => (DataPort & 0x20) != 0},
                {ParallelPortPins.Pin08, () => (DataPort & 0x40) != 0},
                {ParallelPortPins.Pin09, () => (DataPort & 0x80) != 0},
                {ParallelPortPins.Pin10, () => (StatusPort & 0x40) != 0},
                {ParallelPortPins.Pin11, () => (StatusPort & 0x80) != 0},
                {ParallelPortPins.Pin12, () => (StatusPort & 0x20) != 0},
                {ParallelPortPins.Pin13, () => (StatusPort & 0x10) != 0},
                {ParallelPortPins.Pin14, () => (ControlPort & 0x02) != 0},
                {ParallelPortPins.Pin15, () => (StatusPort & 0x08) != 0},
                {ParallelPortPins.Pin16, () => (ControlPort & 0x04) != 0},
                {ParallelPortPins.Pin17, () => (ControlPort & 0x08) != 0},
            };
        }

        private void AssertIsOpened()
        {
            if (!IsOpened)
            {
                throw new InvalidOperationException();
            }
        }

        private void AssertIsClosed()
        {
            if (IsOpened)
            {
                throw new InvalidOperationException();
            }
        }

        #endregion
    }
}
